/*
    该程序用于演示 Linux基于TCP的文件传输  的客户端
    客户端流程: (发送文件)
    1)把待传输的文件名和文件大小告诉服务端
    2)等待服务端的确认报文(文件名和文件大小的确认)
    3)发送文件内容
    4)等待服务端的确认报文(服务端已接收完文件)

    tcp是可靠的协议,所以收到内容后只需要向客户端回复:"ok,我收到了"
    但如果是不可靠的协议,还需要和客户端核对收到的内容
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
using namespace std;

struct st_fileinfo{
   char filename[256];  //文件名
    long filesize;       //文件大小
};

long getFileSize(char* filepath);     //获取文件大小
string getFileName(const string& filepath);  //获取文件名

int main(int argc,char* argv[]){
    
    if(argc != 3){
        printf("using: ./client ip port\n");
        printf("example: ./client 127.0.0.1 55554\n");
        return -1;
    }

    //创建通信套接字
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd == -1){
        perror("fd create failed.");
        return -1;
    }
    //定义服务器地址结构
    struct sockaddr_in srv_addr;
    memset(&srv_addr,0,sizeof(struct sockaddr_in));
    srv_addr.sin_family = AF_INET;   //ipv4
    srv_addr.sin_port = htons(atoi(argv[2]));
    srv_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //连接服务器
    if(connect(fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr)) < 0){
        printf("connect to server( %s ) failed.\n",argv[1]);
        return -1;
    }
    printf("与服务器连接成功!\n");
     char recv_buf[1024]; //接收缓冲区
    char send_buf[1024];   //发送缓冲区

    //与服务器通信
    while(1){
       
        //接收客户端发来的消息      
        memset(recv_buf,0,sizeof(recv_buf));
        recv(fd,recv_buf,sizeof(recv_buf),0);
        printf("%s",recv_buf);         //请输入要传输文件的完整路径: 

        //获取用户输入的文件路径
        memset(send_buf,0,sizeof(send_buf));
        scanf("%[^\n]" , send_buf);
        printf("用户输入:%s\n",send_buf);

        //判断文件是否存在,存在返回0,不存在或者无权限返回-1
        if(access(send_buf,0) == -1){
            printf("文件不存在或者没有权限!\n");
            break;
        }
        char filepath[128]={0};
        strcpy(filepath,send_buf);  //文件路径

        //解析文件名和文件大小,封装成头部信息
        struct st_fileinfo fileinfo;
        memset(&fileinfo,0,sizeof(fileinfo));
        fileinfo.filesize = getFileSize(filepath);       //获取文件大小
        strcpy(fileinfo.filename, getFileName(filepath).c_str());   //获取文件名(不包含路径,但带后缀)
                                                         
        //将文件头部信息发送给服务器
        memset(send_buf,0,sizeof(send_buf));
        memcpy(send_buf,&fileinfo,sizeof(fileinfo)); //结构体转换成字符串
        send(fd,send_buf,sizeof(send_buf),0);   
        printf("发送的结构体:(%s:%d)\n",fileinfo.filename,fileinfo.filesize);

        //接收服务器的确认(ok)
        memset(recv_buf,0,sizeof(recv_buf));
        recv(fd,recv_buf,sizeof(recv_buf),0);
        if(strcmp(recv_buf,"ok") != 0){
            printf("未接收到服务器的ok应答...\n");
            continue;
        }
         printf("接收到服务器的ok应答,开始发送文件...\n");
        //发送文件内容
        FILE* fp = fopen(filepath,"rb");    //二进制方式打开文件
        int onread = 0;     //每次读取的字节数
        int totalbytes = 0; //已经读取的总字节数
        char buf[20]={0};   //存放每次读取数据的缓冲区,每次读取20个字节

        while(true){
            memset(buf,0,sizeof(buf));

            //计算本次应该读取的字节数,如果剩余数据超过20字节,就读20字节
            if(fileinfo.filesize - totalbytes > 20)
                onread = 20;
            else
                onread = fileinfo.filesize - totalbytes;

            //从文件中读取数据
            fread(buf,sizeof(char),sizeof(buf),fp);

            //把读取到的数据发送给服务器
            send(fd,buf,sizeof(buf),0);  
            //printf("发送的数据:%s\n",buf);

            //计算文件已读取的字节总数,如果文件读完,跳出循环
            totalbytes += onread;

            if(totalbytes==fileinfo.filesize)
                break;
        }
        printf("发送完毕\n");

        fclose(fp);

    }

    close(fd);

    return 0;
}

long getFileSize(char* filepath)
{
	int fd = open(filepath, O_RDONLY);	// 打开文件
	if (fd == -1) {
		printf("Open file %s failed : %s\n", filepath, strerror(errno));
		return -1;
	}

    struct stat file_stat;
    /*
    struct stat { 
    dev_t st_dev;       // 文件所在设备ID 
    ino_t st_ino;       // 结点(inode)编号  
    mode_t st_mode;     // 保护模式 
    nlink_t st_nlink;   // 硬链接个数  
    uid_t st_uid;       // 所有者用户ID  
    gid_t st_gid;       // 所有者组ID  
    dev_t st_rdev;      // 设备ID(如果是特殊文件) 
    off_t st_size;      // 总体尺寸，以字节为单位 
    blksize_t st_blksize;   // 文件系统 I/O 块大小
    blkcnt_t st_blocks;     // 已分配 512B 块个数
    time_t st_atime;        // 上次访问时间 
    time_t st_mtime;        // 上次更新时间 
    time_t st_ctime;        // 上次状态更改时间 
};
    */
	int ret = fstat(fd, &file_stat);	// 获取文件状态 成功获取返回 0,失败返回 -1
	if (ret == -1) {
		printf("Get file %s stat failed:%s\n", filepath, strerror(errno));
		close(fd);
		return -1;
	}
	close(fd);
	return file_stat.st_size;
}

string getFileName(const string& filepath)
{   
    int pos = filepath.rfind("/");  //右边开始匹配,返回第一次出现的下标
    if(pos==string::npos)
         return filepath;
    else
        return filepath.substr(pos+1,filepath.length()-pos-1);
}
